/*
 * Created on 2004-01-07
 */
package org.opencube.oms;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;

import org.opencube.oms.meta.ComplexAttribute;
import org.opencube.oms.meta.OMSMetaDataException;
import org.opencube.util.Constants;
import org.opencube.util.Key;

/**
 * @author <a href="mailto:maciek@fingo.pl">FINGO - Maciej Mroczko</a>
 * TODO: comment
 */
public class OMSElement extends OMSNode
{
	/* The structure, that this element is associated with */
	private OMSStructure omsStructure = null;

	/* Element's displayName */
	private String displayName = null;
	/* Scheme path */
	private String namespacePath = null;
	/* Scheme name */
	private String schemeName = null;
	/* The map { simple_attribute_name -> { datatype_enumeration_name - > Value } } */
	private HashMap values = new HashMap();
	/* The map { complex_attribute_name -> array_list_of_relations }; both for compositions and associations */
	HashMap relations = new HashMap( 3 );
	/* The map { namespace_path/scheme_name.complex_attribute_name->array_of_relation */
	private HashMap reflexiveRelations = new HashMap( 1 );
	/* The map { rights_owner_id -> rights } */
	HashMap rights = null;
	/* Rights to remove from database; not all rights on this list have to be really in database */
	ArrayList removedRights = new ArrayList();
	/* The path string */
	private String path;
	/* The element level (set when removed) */
	private int level = -1;
	
	/**
	 * @param elementTree
	 * @param namespacePath
	 * @param schemeName
	 * @param id
	 * @param name
	 * @param bookmark
	 * @param creatingDate
	 * @param creater
	 * @param modifyingDate
	 * @param modifier
	 */	
	protected OMSElement( OMSStructure omsStructure, String namespacePath, String schemeName, String id, String name, String bookmark, Date creatingDate, String creater, Date modifyingDate, String modifier )
	{
		super( id, bookmark, creatingDate, creater, modifyingDate, modifier );
		
		this.displayName = name;
		this.omsStructure = omsStructure;
		this.namespacePath = namespacePath;
		this.schemeName = schemeName;
	}

	/**
	 * Returns the bookmark
	 * 
	 * @return String - the bookmark
	 */
	public String getBookmark()
	{
		return getName();
	}

	/**
	 * Returns the display name
	 * 
	 * @return String - the display name
	 */
	public String getDisplayName()
	{
		return this.displayName;
	}

	/**
	 * Sets the bookmark
	 * 
	 * @param bookmark - new bookmark to set
	 */
	public void setBookmark( String bookmark ) throws OMSStructureException
	{
		if( bookmark == null || bookmark.trim().length() == 0 )
		{
			throw new OMSStructureException( "Null or empty bookmark not allowed" );
		}
		String old = getBookmark();
		update( bookmark );
		this.omsStructure.resetBookmark( old, this );
		if( this.displayName == null )
		{
			setDisplayName( bookmark );
		}
	}
	
	/**
	 * Sets the display name
	 * 
	 * @param displayName - new display name to set
	 */
	public void setDisplayName( String displayName ) throws OMSStructureException
	{
		if( displayName == null || displayName.trim().length() == 0 )
		{
			throw new OMSStructureException( "Null or empty display name not allowed" );
		}
		if( !( displayName == null && this.displayName == null ) && !( displayName != null && displayName.equals( this.displayName ) ) )
		{
			this.displayName = displayName;
			if( !isNew() )
			{
				setState( STATE_UPDATED );
			}
		}
	}

	/**
	 * Return the oms structure, this element is associated with
	 * 
	 * @return OMSStructure
	 */
	public OMSStructure getOMSStructure()
	{
		return this.omsStructure;
	}

	/**
	 * Associates the given element with this one.
	 *  
	 * @param attribute - the attribute for the association
	 * @param element - the element to associate
	 * @param key - the key to join under
=	 */
	public void associate( String attribute, OMSElement element, String key )
	throws OMSStructureException
	{
		associate(attribute, element, key, null );
	}
	
	/**
	 * Associates the given element with this one before the given element.
	 * 
	 * @param attribute
	 * @param element
	 * @param key
	 * @param nextElement
	 * @throws OMSStructureException
	 */
	public void associate( String attribute, OMSElement element, String key, OMSElement nextElement )
	throws OMSStructureException
	{
		
		join( null, attribute, false, element, key, nextElement );
	}
	
	/**
	 * Associates the given element with this one.
	 *  
	 * @param attribute - the attribute for the association
	 * @param element - the element to associate
	 * @param key - the key to join under
=	 */
	public void associate( String attribute, OMSElement element )
	throws OMSStructureException
	{
		associate( attribute, element, null );
	}

	void join( Relation useRelation, String attribute, boolean composition, OMSElement element, String key, OMSElement nextElement )
	throws OMSStructureException
	{
		// composing foreign-structure elements not allowed
		if( composition && element.getOMSStructure() != this.omsStructure )
		{
			throw new OMSStructureException( "Cannot compose element from the foreign structure" );
		}
		// if not moving, and already joined 
		if( useRelation == null &&  getRelation( attribute, element ) != null )
		{
			throw new OMSStructureException( "The element " + element + " is already joined with the element " + this + " by the attribute " + attribute );
		}
		
		if( this == omsStructure.getRootElement() )
		{
			// cannot associate under virtual root
			if( !composition )
			{
				throw new OMSStructureException( "You cannot associate element " + this.toString() + " under the virtual root" );
			}
			else if( nextElement == null )
			{
				throw new OMSStructureException( "Composing under the virtual root is allowed only when the next element is specified" );
			}
		}
		
		// test if next element is joined
		Relation nextRelation = null;
		if( nextElement != null )
		{
			nextRelation = getRelation( attribute, nextElement );
			if( nextRelation == null )
			{
				throw new OMSStructureException( "The element " + nextElement.toString() + " is not joined to the element " + this.toString() + " with attribute '" + attribute + "'" );
			}
		}
		
		if( key == null )
		{
			key = ( useRelation == null ? element.getBookmark() : useRelation.getName() );
		}
		
		// move relation
		if( useRelation != null )
		{
			int state = useRelation.getState();
			// remove it from the old source
			useRelation.getSourceElement().removeRelation( useRelation );
			useRelation = createAndAppendRelation( nextRelation != null ? nextRelation.getSourceElementId() : isRoot() ? useRelation.getSourceElementId() : getId(), element, attribute, null, composition, useRelation.getId(), key, nextRelation, 0, useRelation.getCreater(), useRelation.getCreatingDate(), null, new Date() );
			useRelation.setState( state == OMSNode.STATE_NORMAL ? OMSNode.STATE_UPDATED : state );
		}
		// creates new one
		else
		{
			createAndAppendNewRelation( attribute, composition, element, key, nextRelation );
		}
		
//		if( composition )
//		{
//			OMSElement parent = this.omsStructure.getElementParent( element );
//			if( parent != null )
//			{
//				r = parent.decompose( element );
//			}
//			if( r == null )
//			{
//				createAndAppendNewRelation( attribute, composition, element, key, before );
//			}
//			else
//			{
//				int state = r.getState();
//				r = createAndAppendRelation( element, attribute, composition, r.getId(), key, before, r.getCreater(), r.getCreatingDate(), null, new Date() );
//				if( state == OMSNode.STATE_NORMAL )
//				{
//					r.setState( OMSNode.STATE_UPDATED );
//				}
//				else
//				{
//					r.setState( state );
//				}
//			}
//		}
//		else
//		{
//			createAndAppendNewRelation( attribute, composition, element, key, before );
//		}
	}
	
	/**
	 * Appends the given element to this one.
	 *  
	 * @param attribute - the attribute for the composition
	 * @param element - the element to associate
	 */
	public String getPath() { 
		return path;
	}
	void setPath(String path){
		this.path = path;
	}
	public void compose( String attribute, OMSElement element )
	throws OMSStructureException
	{
		compose( attribute, element, null );
	}

	/**
	 * Appends the given element to this one.
	 *  
	 * @param attribute - the attribute for the composition
	 * @param element - the element to append
	 * @param key - the key to join under
=	 */
	public void compose( String attribute, OMSElement element, String key )
	throws OMSStructureException
	{
		compose( attribute, element, key, null );
	}

	/**
	 * Appends the given element to this one before the given element.
	 *  
	 * @param attribute - the attribute for the composition
	 * @param element - the element to append
	 * @param key - the key to join under
	 * @param nextElement - the key to join under
=	 */
	public void compose( String attribute, OMSElement element, String key, OMSElement nextElement )
	throws OMSStructureException
	{
		if( element.getParent() != null && element.getParent().getRelation( element.getParentComplexAttributeName(), element ) != null )
		{
			throw new OMSStructureException( "The element " + element + " is already composed by element " + element.getParent() );
		}
		join( null, attribute, true, element, key, nextElement );
	}

	/**
	 * Marks the association with the element as removed
	 * 
	 * @param attribute - attribute to search for the element under 
	 * @param element - the element to disassociate
	 */
	public void disconnect( String attribute, OMSElement element ) throws OMSStructureException
	{
		Relation r = getRelation( attribute, element );
		if( r != null )
		{
			r.delete();
		}
	}
	
	/**
	 * Returns all elments related to this one, connected with the given attribute
	 * 
	 * @param attribute - the relation attribute
	 * 
	 * @return
	 * 
	 * @throws OMSMetaDataException - when the given attribute not found
	 */
	public OMSElement[] getJoinedElements( String attribute )
	{
		Relation[] relations = getRelationsByComplexAttribute( attribute );
		ArrayList result = new ArrayList( relations == null ? 0 : relations.length );
		for( int i = 0; relations != null && i < relations.length; i++ )
		{
			result.add( relations[ i ].getTargetElement() );
		}
		
		return (OMSElement[]) result.toArray( new OMSElement[ result.size() ] );
	}
	
	/**
	 * Returns newest modification date for all relations with given attribute
	 * 
	 * @param attribute - the relation attribute
	 * 
	 * @return Date - modification date
	 * 
	 * @throws OMSMetaDataException - when the given attribute not found
	 */
	public Date getRelationsLastModifyingDate( String attribute )
	{
		Relation[] relations = getRelationsByComplexAttribute( attribute );
		Date date = null;
		for( int i = 0; relations != null && i < relations.length; i++ )
		{
			if( date == null || date.before( relations[ i ].getModifyingDate() ))
			{
				date = relations[ i ].getModifyingDate();
			}
		}
		return date;
	}
	
	public OMSElement[] getJoiningElements( String namespacePath, String schemeName, String attributeName )
	{
		ArrayList result = (ArrayList) this.reflexiveRelations.get( getJoiningKey( namespacePath, schemeName, attributeName ) );
		ArrayList list = new ArrayList();
		for( int i = 0; result != null && i < result.size(); i++ )
		{
			list.add( ( (Relation) result.get( i ) ).getSourceElement() );
		}
		
		return (OMSElement[]) list.toArray(new OMSElement[list.size()]);
	}
	
	/**
	 * Sets the value for the given attribute in the specified domain value
	 *  
	 * @param attribute the attribute to set the value for
	 * @param datatypeEnumeration the datatype enumeration to set the value for
	 * @param value the value to set
	 * 
	 * @throws OMSMetaDataException when one of the following occurs:<br>
	 * <ul>
	 *   <li>attribute not found in this element scheme hierarchy</li>
	 * 	 <li>the datatype enumeration value doesn't belong to the attribute datatype</li>
	 * 	 <li>the value doesn't match the attribute constraints</li>
	 * </ul>  
	 */
	public void setValue( String attribute, String datatypeEnumeration, Object value )
	throws OMSStructureException
	{
		if( value instanceof String && value != null && ( (String) value).trim().length() == 0 )
		{
			value = null;
		}
		Value svl = getElementValue( attribute, datatypeEnumeration );
		if( svl == null && value != null )
		{
			svl = createAndAppendNewValue( attribute, datatypeEnumeration, value );
		}
		else if( svl != null )
		{
			if( value == null )
			{
				svl.delete();
			}
			else
			{
				svl.setContent( value );
			}
		}
	}
	
	/**
	 * Returns the value of the given attribute in the specified domain value
	 *  
	 * @param attribute the attribute to get the value for
	 * @param domainValue the domain value to get the value for
	 * @return the value
	 * 
	 * @throws OMSMetaDataException when one of the following occurs:<br>
	 * <ul>
	 *   <li>attribute not found in this element scheme hierarchy</li>
	 * 	 <li>the domain value doesn't belong to the attribute domain</li>
	 * 	 <li>the value doesn't match the attribute constraints</li>
	 * </ul>  
	 */
	public Object getValue( String attribute, String datatypeEnumeration )
	{
		Value svl = getElementValue( attribute, datatypeEnumeration );
		if( svl != null )
		{
			
			return svl.getContent();
		}
		else
		{
			return null;
		}
	}
	
	/**
	 * Returns modifying date for value of the given attribute in the specified domain value
	 *  
	 * @param attribute the attribute to get the value for
	 * @param domainValue the domain value to get the value for
	 * @return modification date
	 * 
	 * @throws OMSMetaDataException when one of the following occurs:<br>
	 * <ul>
	 *   <li>attribute not found in this element scheme hierarchy</li>
	 * 	 <li>the domain value doesn't belong to the attribute domain</li>
	 * 	 <li>the value doesn't match the attribute constraints</li>
	 * </ul>  
	 */
	public Date getValueModifyingDate( String attribute, String datatypeEnumeration )
	{
		Value svl = getElementValue( attribute, datatypeEnumeration );
		if( svl != null )
		{
			return svl.getModifyingDate();
		}
		else
		{
			return null;
		}
	}
	 
	/**
	 * Returns all values
	 * 
	 * @return Value[] - all values
	 */
	Value[] getValues()
	{
		ArrayList list = new ArrayList();
		
		Iterator it = this.values.values().iterator();
		while( it.hasNext() )
		{
			HashMap l = (HashMap) it.next();
			if( l != null )
			{
				list.addAll( l.values() );
			}
		} 
		
		return (Value[]) list.toArray( new Value[ list.size() ] );
	}
	
	/**
	 * Returns value with the given id
	 * 
	 * @param attribute
	 * @param datatypeEnumeration
	 * 
	 * @return The value with the given id or null if not found
	 */	
	Value getElementValue( String attribute, String datatypeEnumeration )
	{
		HashMap map = (HashMap) this.values.get( Key.getKey( attribute ) );
		if( map != null )
		{
			return (Value) map.get( Key.getKey( datatypeEnumeration ) );
		}
		return null;
	}
	
	/**
	 * Creates and appends relation
	 * 
	 * @param targetElement
	 * @param complexAttribute
	 * @param id
	 * @param key
	 * @param creater
	 * @param creatingDate
	 * @param modifier
	 * @param modifyingDate
	 * 
	 * @return Relation - created relation
	 * 
	 * @throws OMSMetaDataException
	 */
	Relation createAndAppendRelation( String sourceElementId, OMSElement targetElement, String relationAttribute, String relationAttributeId, boolean composition, String id, String key, Relation next, int siblingOrder, String creater, Date creatingDate, String modifier, Date modifyingDate )
	throws OMSStructureException
	{
		String joiningAttribute = isRoot() ? OMSStructure.OBJECTS_ATTRIBUTE : relationAttribute;
		if( next != null )
		{
			relationAttribute = next.getComplexAttribute();
		}
		ArrayList array = (ArrayList) this.relations.get( Key.getKey( joiningAttribute ) );
		if( array == null )
		{
			array = new ArrayList( 1 );
			this.relations.put( Key.getKey( joiningAttribute ), array );
		}
		Relation relation = null;
		for( int i = 0; i < array.size(); i++ )
		{
			if( targetElement != null &&  targetElement == ( (Relation) array.get( i ) ).getTargetElement() )
			{
				relation = (Relation) array.get( i );
			}
		}
		if( relation == null )
		{
			relation = new Relation( this, sourceElementId, targetElement, relationAttribute, relationAttributeId,composition, siblingOrder, id, key, creater, creatingDate, modifier, modifyingDate );
			// the next element was specified
			relation.setOrdered( next != null );
			boolean inserted = false;
	
			// insert before the 'next' relation
			
			if( relation.isOrdered() )
			{
				for( int i = 0; array != null && i < array.size(); i++ )
				{
					Relation r = (Relation) array.get( i );
					if( r.getId().equals( next.getId() ) )
					{
						array.add( i, relation );
						inserted = true;
						break;
					}
				}
			}
			// use order sibling 
			if( siblingOrder > 0 && !inserted )
			{
				for( int i = 0; array != null && i < array.size(); i++ )
				{
					Relation r = (Relation) array.get( i );
					if( r.getSiblingOrder() > relation.getSiblingOrder() )
					{
						array.add( i, relation );
						inserted = true;
						break;
					}
				}
			}
			// simply append
			if( !inserted )
			{
				array.add( relation );
			}
		}
		if( composition )
		{
			this.omsStructure.setElementParent( targetElement, this );
		}
		// reflexive relation
		Key joiningKey = getJoiningKey( this.namespacePath, this.schemeName, joiningAttribute );
		ArrayList targetRelations = (ArrayList) targetElement.reflexiveRelations.get( joiningKey );
		if( targetRelations == null )
		{
			targetRelations = new ArrayList( 1 );
			targetElement.reflexiveRelations.put( joiningKey, targetRelations );
		}
		if( !targetRelations.contains( relation )  )
		{
			targetRelations.add( relation );
		}
		return relation;
	}
	
	private Key getJoiningKey( String namespacePath, String schemeName, String attributeName )
	{
		return Key.getKey( namespacePath + Constants.STR_SLASH + schemeName + Constants.STR_DOT + attributeName );
	}
	
	/**
	 * Creates and appends new relation
	 * 
	 * @param complexAttribute
	 * @param targetElement
	 * 
	 * @return Newly created relation
	 * 
	 * @throws OMSMetaDataException
	 */	
	Relation createAndAppendNewRelation( String complexAttribute, boolean composition, OMSElement targetElement, String key, Relation before )
	throws OMSStructureException
	{
		String newIndex = this.omsStructure.getNewId();
		Relation r = createAndAppendRelation( this.getId(), targetElement, complexAttribute, null,composition, newIndex, key, before, 0, null, new Date(), null, new Date() );
		r.setState( OMSNode.STATE_NEW );
		
		return r;
	}

	/**
	 * Returns all relations
	 * 
	 * @return Relation[] - all element's relations
	 */
	Relation[] getRelations()
	{
		ArrayList list = new ArrayList();
		
		Iterator it = this.relations.values().iterator();
		while( it.hasNext() )
		{
			ArrayList l = (ArrayList) it.next();
			if( l != null )
			{
				list.addAll( l );
			}
		} 
		
		return (Relation[]) list.toArray( new Relation[ list.size() ] );
	}

	/**
	 * Returns all relations for the given attribute
	 * 
	 * @param attribute - the attribute name to get the relations for
	 * 
	 * @return Relation[] - all relations for the given attribute
	 */
	Relation[] getRelationsByComplexAttribute( String attribute )
	{
		ArrayList list = (ArrayList) this.relations.get( Key.getKey( attribute ) );
		if( list != null )
		{
			return (Relation[]) list.toArray( new Relation[ list.size() ] );
		}
		return null;
	}
	Relation getRelation( String attribute, OMSElement related )
	{
		Relation[] relations = getRelationsByComplexAttribute( attribute );
		for (int i = 0; relations != null && i < relations.length; i++)
		{
			if( relations[ i ].getTargetElement() == related )
			{
				return relations[ i ];
			}
		}
		return null;
	}

	/**
	 * Completely removes the given value from this element
	 *  
	 * @param value - the value to remove
	 */
	void removeValue( String attribute, String datatypeEnumeration )
	{
		HashMap map = (HashMap) this.values.get( Key.getKey( attribute ) );
		if( map != null )
		{
			map.remove( Key.getKey( datatypeEnumeration ) );
		}
	}
	
	
	private void removeJoinedRelation( Relation relation) { 
		ArrayList l = (ArrayList) this.relations.get( Key.getKey( isRoot() ? OMSStructure.OBJECTS_ATTRIBUTE : relation.getComplexAttribute() ) );
		if( l != null )
		{
			l.remove( relation );
		}
	}
	/**
	 * Completely removes the given relation from this element
	 * 
	 * @param relation - the relation to remove
	 */
	void removeRelation( Relation relation )
	{
		removeJoinedRelation(relation);
		relation.getTargetElement().removeReflexiveRelation( relation );
	}
	
	void removeReflexiveRelation( Relation relation )
	{
		Key key = getJoiningKey( relation.getSourceElement().getNamespacePath(), relation.getSourceElement().getSchemeName(), relation.getSourceElement().isRoot() ? OMSStructure.OBJECTS_ATTRIBUTE : relation.getComplexAttribute() );
		ArrayList list = (ArrayList) this.reflexiveRelations.get( key );
		if( list != null )
		{
			list.remove( relation );
			if( list.size() == 0 )
			{
				this.reflexiveRelations.remove( key );
			}
		}
	}
	
	
	public String getParentComplexAttributeName()
	{
		OMSElement parent = getParent();
		if( parent != null )
		{
			Relation composition = parent.getCompositionRelation( this );
			if( composition != null )
			{
				return parent.isRoot() ? OMSStructure.OBJECTS_ATTRIBUTE : composition.getComplexAttribute();
			}
		}
		return null;
	}
	
	/**
	 * Removes all relations with the given element
	 * 
	 * @param targetElement - element for which remove all relations
	 */
	void removeRelations( OMSElement targetElement )
	{
		Iterator it = this.relations.values().iterator();
		LinkedList toRemove = new LinkedList();
		while( it.hasNext() )
		{
			ArrayList map = (ArrayList) it.next();
			Iterator itInternal = map.iterator();
			while( itInternal.hasNext() )
			{
				Relation r = (Relation) itInternal.next();
				if( r.getTargetElement().equals( targetElement ) )
				{
					toRemove.add( r );
				}
			}
		}
		for( int i = 0; i < toRemove.size(); i++ )
		{
			removeRelation( (Relation) toRemove.get( i ) );
		}
	}

	Value createAndAppendNewValue( String attribute, String datatypeEnumeration, Object value )
	{
		Value svl = createAndAppendValue( datatypeEnumeration, attribute, this.omsStructure.getNewId(), value, new Date(), null, new Date(), null );
		svl.setState( STATE_NEW );
		
		return svl;
	}
	
	Value createAndAppendValue( String den, String sat, String id, Object value, Date created, String creater, Date modified, String modifier  )
	{
		Value svl = new Value( this, den, sat, id, value, created, creater, modified, modifier );
		HashMap attVals = (HashMap) this.values.get( Key.getKey( sat ) );
		if( attVals == null )
		{
			attVals = new HashMap( 1 );
			this.values.put( Key.getKey( sat ), attVals );
		}
		attVals.put( Key.getKey( den ), svl );
		return svl;
	}
	/**
	 * @return
	 */
	int getLevel()
	{
		if( this.level != -1 )
		{
			return this.level;
		}
		int l = 0;
		OMSElement parent = this.omsStructure.getElementParent( this );
		while( parent != null )
		{
			 l++;
			 parent = parent.omsStructure.getElementParent( parent );
		}
		return l;
	}

	void setOMSStructure( OMSStructure tree )
	{
		this.omsStructure = tree;
	}
	
	public OMSElement getParent()
	{
		return this.omsStructure.getElementParent( this );
	}
	
	public void setId( String newId )
	{
		String old = this.id;
		super.setId( newId );
		if( old != null )
		{
			this.omsStructure.resetId( old, this );
		}
	}
    
    private HashMap cloneValues(HashMap values){
        Object[] keys=values.keySet().toArray();
        HashMap cloneHashMap = new HashMap();
        for(int i=0;i<keys.length;i++){
            String key = "";
           
            HashMap valueMap=(HashMap)values.get(keys[i]);    
            HashMap cloneValueMap = new HashMap();
            Object[] datatypeEnumAsKeys=valueMap.keySet().toArray();
            for(int j=0;j<datatypeEnumAsKeys.length;j++){
                Value value = (Value)valueMap.get(datatypeEnumAsKeys[j]);
                Value  cloneValue = new Value (value.getElement(),
                        value.getDatatypeEnumeration(),
                        value.getSimpleAttribute(),
                        value.getId(),
                        value.getContent(),
                        value.getCreatingDate(),
                        value.getCreater(),
                        value.getModifyingDate(),
                        value.getModifier());
                cloneValue.setState(value.getState());
                cloneValue.name = value.getName();
                key=  value.getSimpleAttribute();
                cloneValueMap.put(datatypeEnumAsKeys[j],cloneValue);
            }
         
            cloneHashMap.put(((Key)keys[i]).getKey(key),cloneValueMap);
        }
        return cloneHashMap;
    }
    
    private boolean isInComplexAttribute(ComplexAttribute[] complexAttributes,String complexAttributeNameId){
    	for (int i=0;i<complexAttributes.length;i++) { 
    		if (complexAttributes[i].getId().equals(complexAttributeNameId)) { 
    			return true;
    		}
    	}
    	return false;
    	
    }
	/**
	 * Merges the relations of the elements
	 * (maybe something more should be done?)
	 * @param element
	 */
	protected void merge(OMSElement element,OMSStructure foreignStructure, int mode,ComplexAttribute[] complexAttributes) throws OMSStructureException {
        Relation[] myRelations = getRelations();
        Relation[] foreignRelations = element.getRelations();

        // replace content of the element and
        if (mode == OMSStructure.MERGE_MODE_REPLACE || mode == OMSStructure.MERGE_MODE_REFRESH && element != this) {
           
                this.values = element.values;
            
            
            this.setDisplayName(element.getDisplayName());
            this.setBookmark(element.getBookmark());
            this.modifyingDate = element.getModifyingDate();
            this.creatingDate = element.getCreatingDate();
            this.creater = element.getCreater();
            this.modifier = element.getModifier();
            if (mode == OMSStructure.MERGE_MODE_REPLACE ) {
                for (int i = 0; myRelations != null && i < myRelations.length; i++) {
                    removeRelation(myRelations[i]);
                }
            }
            if (mode == OMSStructure.MERGE_MODE_REFRESH && complexAttributes!=null ) {
                for (int i = 0; myRelations != null && i < myRelations.length; i++) {
                	if (myRelations[i].getComplexAttributeId()==null) { 
                		this.omsStructure.throwComplexAttributeException(myRelations[i]);
                	}
                	if (isInComplexAttribute(complexAttributes,myRelations[i].getComplexAttributeId())) {
                		
                	//myRelations[i].getComplexAttribute()
                		//removeRelation(myRelations[i]);
                		removeJoinedRelation(myRelations[i]);
                	}
                }
            }
        }

        if (mode != OMSStructure.MERGE_MODE_REMOVE) {
            outer: for (int i = 0; foreignRelations != null && i < foreignRelations.length; i++) {
                Relation r2Add = foreignRelations[i];
              

                OMSElement target = this.omsStructure.getElementById(r2Add.getTargetElement().getId());
                // skip the existing relations
                for (int j = 0; target == r2Add.getTargetElement() && myRelations != null && j < myRelations.length; j++) {
                    if (r2Add.getId().equals(myRelations[j].getId())) {
                        continue outer;
                    }
                }
                // remove the reflexive relation to the old source
                r2Add.getTargetElement().removeReflexiveRelation(r2Add);
                r2Add.setSourceElement(this);

                // if the relation target found in the source element structure,
                // change the target to this one
                OMSElement targetElement = this.omsStructure.getElementById(r2Add.getTargetElement().getId());
                boolean existingRelation = targetElement != null && getRelation(r2Add.getComplexAttribute(), targetElement) != null;
                if (targetElement != null && targetElement != r2Add.getTargetElement()) {
                    r2Add.setTargetElement(targetElement);
                }
                 
                // add to target the reflexive relation to the new source
                Key key = getJoiningKey(getNamespacePath(), getSchemeName(), r2Add.getComplexAttribute());
                ArrayList joiningRelationsArray = (ArrayList) r2Add.getTargetElement().reflexiveRelations.get(key);
                if (joiningRelationsArray == null) {
                    joiningRelationsArray = new ArrayList(1);
                    r2Add.getTargetElement().reflexiveRelations.put(key, joiningRelationsArray);
                }
                boolean contains = false;
                for (int j = 0; j < joiningRelationsArray.size(); j++) {
                    if (((Relation) joiningRelationsArray.get(j)).getSourceElement() == r2Add.getSourceElement()) {
                        contains = true;
                    }
                }
                if (contains) {
                    joiningRelationsArray.add(r2Add);
                }

                // add the relation
                if (!existingRelation) {
                    Relation added = createAndAppendRelation(r2Add.getSourceElementId(), r2Add.getTargetElement(), r2Add.getComplexAttribute(), r2Add.getComplexAttributeId(), r2Add.isComposition(), r2Add.getId(), r2Add.getName(), r2Add.isOrdered() ? r2Add.getNextRelation() : null, r2Add.getSiblingOrder(), r2Add.getCreater(),
                            r2Add.getCreatingDate(), r2Add.getModifier(), r2Add.getModifyingDate());
                    added.setState(r2Add.getState());
                }
            }
        }
    }
	
	/**
	 * Marks the element as deleted, moves it to the structure trash and removes 
	 * all it's relations as well, as all the relations to this element.
	 * 
	 * @throws OMSStructureException when this element composes any other
	 */
	public void delete() throws OMSStructureException
	{
		// test if may be deleted
		Relation[] relations = getRelations();
		for (int i = 0; relations != null && i < relations.length; i++)
		{
			if( relations[ i ].isComposition() )
			{
				throw new OMSStructureException( "The element id = '" + getId() + "', bookmark = '" + getBookmark() + "' cannot be deleted until it composes id = '" + relations[ i ].getTargetElement().getId() + "', bookmark = '" + relations[ i ].getTargetElement().getBookmark() + "' under " + relations[ i ].getComplexAttribute() );
			}
		}
		if( isNew() )
		{
			omsStructure.removeElement( this, true, true );
		}
		else
		{
			// remember level before removing
			this.level = getLevel();
			// mark deleted
			super.delete();
			// remove all relations
			for (int i = 0; relations != null && i < relations.length; i++)
			{
				relations[ i ].delete();
			}
			// remove from all joining
			Relation[] joiningRelations = getReflexiveRelations();
			for (int i = 0; joiningRelations != null && i < joiningRelations.length; i++)
			{
				joiningRelations[ i ].getSourceElement().disconnect( joiningRelations[ i ].getComplexAttribute(), this );
			}
			// move to trash
			this.omsStructure.getElementTrash().add( this );
			this.omsStructure.removeElement( this, false, false );
		}
	}
	
	Relation[] getReflexiveRelations()
	{
		ArrayList joiningRelations = new ArrayList();
		for (Iterator iter = this.reflexiveRelations.values().iterator(); iter.hasNext();)
		{
			ArrayList relationList = (ArrayList) iter.next();
			ArrayList newList = new ArrayList();
			for( int i = 0; relationList != null && i < relationList.size(); i++ )
			{
				joiningRelations.add( relationList.get( i ) );
			}
		}
		return (Relation[]) joiningRelations.toArray(new Relation[joiningRelations.size()]);
	}

	/**
	 * @return Returns the namespacePath.
	 */
	public String getNamespacePath()
	{
		return this.namespacePath;
	}
	/**
	 * @return Returns the schemeName.
	 */
	public String getSchemeName()
	{
		return this.schemeName;
	}
	
	public void moveCompositionAppend( OMSElement toElement, String toAttribute )
	throws OMSStructureException
	{
		move( getParent(), getParentComplexAttributeName(), toElement, toAttribute );
	}
	
	public void moveCompositionBefore( OMSElement nextElement )
	throws OMSStructureException
	{
		move( getParent(), getParentComplexAttributeName(), nextElement.getParent(), nextElement.getParentComplexAttributeName(), nextElement );
	}
	
	public void moveAssociationAppend( OMSElement fromElement, String fromAttribute, OMSElement toElement, String toAttribute )
	throws OMSStructureException
	{
		move( fromElement, fromAttribute, toElement, toAttribute );
	}
	
	public void moveAssociationBefore( OMSElement fromElement, String fromAttribute, OMSElement toElement, String toAttribute, OMSElement nextElement )
	throws OMSStructureException
	{
		move( fromElement, fromAttribute, toElement, toAttribute, nextElement );
	}
	
	/**
	 * Moves this element. 
	 *  
	 * @param fromElement the element to move this element from
	 * @param fromAttribute the attribute name of the relation 'fromElement' to this
	 * @param toElement the element to move this element to
	 * @param toAttribute the attribute name to join this element to the 'toElement' under 
	 * 
	 * @throws OMSStructureException
	 */
	public void move( OMSElement fromElement, String fromAttribute, OMSElement toElement, String toAttribute )
	throws OMSStructureException
	{
		move( fromElement, fromAttribute, toElement, toAttribute, null );
	}

	/**
	 * Moves this element as left sibling of the 'nextElement'. 
	 *  
	 * @param fromElement the element to move this element from
	 * @param fromAttribute the attribute name of the relation 'fromElement' to this
	 * @param toElement the element to move this element to
	 * @param toAttribute the attribute name to join this element to the 'toElement' under 
	 * @param nextElement if present, this element is moved as its left sibling
	 * 
	 * @throws OMSStructureException
	 */
	public void move( OMSElement fromElement, String fromAttribute, OMSElement toElement, String toAttribute, OMSElement nextElement )
	throws OMSStructureException
	{
		Relation relation = fromElement.getRelation( fromAttribute, this );
		// the moved relation must exist
		if( relation == null )
		{
			throw new OMSStructureException( "The element " + this.toString() + " is not joined to the element " + fromElement.toString() + " with '" + fromAttribute + "' attribute" );
		}
		boolean composition = relation.isComposition();
		
		toElement.join( relation, toAttribute, composition, this, relation.getName(), nextElement );
	}
	
	/**
	 * Returns the key of the relation of this element to the given one.
	 *  
	 * @param attribute
	 * @param related
	 * @return
	 */
	public String getKey( String attribute, OMSElement related )
	{
		Relation r = getRelation( attribute, related );
		return r == null ? null : r.getName();
	}
    
    public void setKey(String attribute,OMSElement related,String keyName){
        Relation r = getRelation( attribute, related );
        if (!r.name.equals(keyName)) { 
        	r.name=keyName;
        	if (r.getState()!=OMSNode.STATE_UPDATED) { 
        		r.setState(OMSNode.STATE_KEYUPDATED);
        	}
        }
    }
	
	/**
	 * Returns the element joined to this one under the given attribute with 
	 * the specified relation key
	 * 
	 * @param attribute
	 * @param key
	 * @return
	 */
	public OMSElement getJoinedElementByKey( String attribute, String key )
	{
		Relation[] relations = getRelationsByComplexAttribute( attribute );
		for (int i = 0; relations != null && i < relations.length; i++)
		{
			if( relations[ i ].getName().equals( key ) )
			{
				return relations[ i ].getTargetElement();
			}
		}
		return null;
	}
	
	/**
	 * Get all rights for this elements
	 * @return OMSRights rights array, can be empty
	 */
	public OMSRights[] getRightsArray()
	{
		if( rights == null )
		{
			return new OMSRights[0];
		}
		return (OMSRights[]) rights.values().toArray(new OMSRights[rights.values().size()]);
	}
	
	/**
	 * Get rights for this elements granted for specified another element
	 * @param grantedTo OMSElements the rights are granted to
	 * @return OMSRigts object or null if not found
	 */
	public OMSRights getRights( OMSElement grantedTo )
	{
		return rights != null ? (OMSRights) rights.get( grantedTo.getId() ) : null;
	}
	
	/**
	 * Add or replace rights
	 * Rights are stored based on owner (granted to) element - putting rigts for existing owner will replace the rights 
	 * @param rights OMSRights to define
	 */
	public void putRights( OMSRights rights )
	{
		if( this.rights == null )
		{
			this.rights = new HashMap();
		}
		if( this.rights.put( rights.getGrantedToId(), rights ) == null )
		{
            rights.setRoot(true);
			rights.setState( OMSNode.STATE_NEW );			
		}
	}
	
	/**
	 * Remove rigts
	 * Nothing happens for rights not found in the element (on rights owner basis)
	 * @param rights OMSRights to remove
	 */
	public void removeRights( OMSRights rights )
	{
		if( rights.isRoot() )
		{
			rights.setState( OMSNode.STATE_REMOVED );
			removedRights.add( rights );
		}
		if( this.rights != null )
		{
			this.rights.remove( rights.getGrantedToId() );
		}
	}
	
	/**
	 * Remove all rights for this element
	 */
	public void removeAllRights()
	{
		OMSRights[] rights = getRightsArray();
		for( int i = 0; i < rights.length; i++ )
		{
			removeRights( rights[ i ] );
		}
	}
	
	/*
	 * Discard any changes in rights
	 */
	void resetRights()
	{
		this.removedRights = new ArrayList();
		this.rights = new HashMap();
	}
	
	/*
	 * Discard any changes in rights and fill it again
	 */
	void resetRights( OMSRights[] rights )
	{
		this.removedRights = new ArrayList();
		this.rights = new HashMap();
		for( int i = 0; i < rights.length; i++ )
		{
			this.rights.put( rights[ i ].getGrantedToId(), rights[ i ] );
		}
	}
	
	public String toString()
	{
		return "[ id = '" + getId() + "', bookmark = '" + getBookmark() + "', displayName = '" + getDisplayName() + "', scheme = '" + getNamespacePath() + Constants.STR_SLASH + getSchemeName() + "' ]";
	}
	
	OMSRights[] getRemovedRightsArray()
	{
		return (OMSRights[]) removedRights.toArray(new OMSRights[removedRights.size()]);
	}
	
	private OMSElement simpleClone( OMSStructure tree, OMSElement parent )
	{
		OMSElement clone = tree.createAndAppendElement( parent, getNamespacePath(), getSchemeName(), getId(), getName(), getBookmark(), getCreatingDate(), getCreater(), getModifyingDate(), getModifier() );
		clone.setState( getState() );
		
		Value[] values = getValues();
		for (int i = 0; values != null && i < values.length; i++)
		{
			clone.createAndAppendValue( values[ i ].getDatatypeEnumeration(), values[ i ].getSimpleAttribute(), values[ i ].getId(), values[ i ].getContent(), values[ i ].getCreatingDate(), values[ i ].getCreater(), values[ i ].getModifyingDate(), values[ i ].getModifier() ).setState( values[ i ].getState() );
		}
		
		return clone;
	}
	public OMSElement replicateRecursive() throws OMSStructureException
	{
		OMSStructure tree = new OMSStructure();
        
        cloneElementNodeRecursive( tree, tree.getRootElement() );    
		
        
        
        
        OMSElement[] els = tree.getElements();
		for (int i = 0; i < els.length; i++)
		{
			OMSElement originalElement = getOMSStructure().getElementById( els[ i ].getId() );
			Relation[] rels = originalElement.getRelations();
			for (int j = 0; j < rels.length; j++)
			{
				if( rels[ j ].isAssociation() )
				{
					OMSElement target = tree.getElementById( rels[ j ].getTargetElement().getId() );
					if( target != null )
					{
						Relation relClone = els[ i ].createAndAppendRelation( rels[ j ].getSourceElementId(), target, rels[ j ].getComplexAttribute(), rels[ j ].getComplexAttributeId(), false, rels[ j ].getId(), rels[ j ].getName(), rels[ j ].getNextRelation(), rels[ j ].getSiblingOrder(), rels[ j ].getCreater(), rels[ j ].getCreatingDate(), rels[ j ].getModifier(), rels[ j ].getModifyingDate() );
						relClone.setState( rels[ j ].getState() );
					}
				}
			}
		}

		return this.isRoot()?tree.getRootElement():tree.getElementById(this.getId());
	}
	public OMSElement replicate() throws OMSStructureException
	{
		OMSStructure tree = new OMSStructure();
		OMSElement clone = cloneElementNode( tree, null );
		OMSElement[] els = tree.getElements();
		for (int i = 0; i < els.length; i++)
		{
			OMSElement originalElement = getOMSStructure().getElementById( els[ i ].getId() );
			Relation[] rels = originalElement.getRelations();
			for (int j = 0; j < rels.length; j++)
			{
				if( rels[ j ].isAssociation() )
				{
					OMSElement target = tree.getElementById( rels[ j ].getTargetElement().getId() );
					if( target != null )
					{
						Relation relClone = els[ i ].createAndAppendRelation( rels[ j ].getSourceElementId(), target, rels[ j ].getComplexAttribute(),rels[ j ].getComplexAttributeId(), false, rels[ j ].getId(), rels[ j ].getName(), rels[ j ].getNextRelation(), rels[ j ].getSiblingOrder(), rels[ j ].getCreater(), rels[ j ].getCreatingDate(), rels[ j ].getModifier(), rels[ j ].getModifyingDate() );
						relClone.setState( rels[ j ].getState() );
					}
				}
			}
		}
		return clone;
	}
	
	private OMSElement cloneElementNode( OMSStructure tree, OMSElement parent )
	throws OMSStructureException
	{
		OMSElement clone = simpleClone( tree, parent );
		Relation[] rels = getRelations();
		for (int i = 0; i < rels.length; i++)
		{
			if( rels[ i ].isComposition() )
			{
				OMSElement child = cloneElementNode( tree, clone );
				Relation rel = clone.createAndAppendRelation( rels[ i ].getSourceElementId(), child, rels[ i ].getComplexAttribute(),rels[ i ].getComplexAttributeId(), true, rels[ i ].getId(), rels[ i ].getName(), rels[ i ].getNextRelation(), rels[ i ].getSiblingOrder(), rels[ i ].getCreater(), rels[ i ].getCreatingDate(), rels[ i ].getModifier(), rels[ i ].getModifyingDate() );
				rel.setState( rels[ i ].getState() );
			}
		}
		return clone;
	}
	private void cloneElementNodeRecursive( OMSStructure tree, OMSElement parent )
	throws OMSStructureException
	{
		
		Relation[] rels = getRelations();
		for (int i = 0; i < rels.length; i++)
		{
			if( rels[ i ].isComposition() )
			{
                OMSElement child = rels[i].getTargetElement().simpleClone( tree, parent );
				rels[i].getTargetElement().cloneElementNodeRecursive( tree, child );
				Relation rel = parent.createAndAppendRelation( rels[ i ].getSourceElementId(), child, rels[ i ].getComplexAttribute(),rels[ i ].getComplexAttributeId(), true, rels[ i ].getId(), rels[ i ].getName(), rels[ i ].getNextRelation(), rels[ i ].getSiblingOrder(), rels[ i ].getCreater(), rels[ i ].getCreatingDate(), rels[ i ].getModifier(), rels[ i ].getModifyingDate() );
				rel.setState( rels[ i ].getState() );
			}
		}
	}
	Relation getCompositionRelation( OMSElement child )
	{
	    Relation[] rels = getRelations();
	    for( int i = 0; i < rels.length; i++ ) 
	    {
	        if( rels[ i ].getTargetElement() == child && rels[ i ].isComposition() )
	        {
	            return rels[ i ];
	        }
	    }
	    return null;
	}
	
	boolean isRoot()
	{
		return this == omsStructure.getRootElement();
	}
	
	/**
	 * 
	 */
	public OMSElement getNextElement()
	{
		Relation r = getCompositionRelation( this );
		return r == null ? null : r.getNextRelation().getTargetElement();
	}

   
}
